using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Configuration;
using org.jawk.ext;
using System.Text;

namespace org.jawk
{
    /// <summary>
    /// Entry point into the parsing, analysis, and execution/compilation
    /// of a Jawk script.
    /// </summary>
    /// <remarks>
    /// Entry point into the parsing, analysis, and execution/compilation
    /// of a Jawk script.
    /// <p>
    /// The overall process to execute a Jawk script is as follows:
    /// <ul>
    /// <li>Parse the Jawk script, producing an abstract syntax tree.
    /// <li>Traverse the abstract syntax tree, producing a list of
    /// instruction tuples for the interpreter.
    /// <li>Either:
    /// <ul>
    /// <li>Traverse the list of tuples, providing a runtime which
    /// ultimately executes the Jawk script, <strong>or</strong>
    /// <li>Translate the list of tuples into JVM code, providing
    /// a compiled representation of the script to JVM.
    /// </ul>
    /// Command-line parameters dictate which action is to take place.
    /// </ul>
    /// Two additional semantic checks on the syntax tree are employed
    /// (both to resolve function calls for defined functions).
    /// As a result, the syntax tree is traversed three times.
    /// And the number of times tuples are traversed is depends
    /// on whether interpretation or compilation takes place.
    /// As of this writing, Jawk traverses the tuples once for
    /// interpretation, and two times for compilation (once for
    /// global variable arrangement, and the second time for
    /// translation to bytecode).
    /// <p>
    /// By default a minimal set of extensions are automatically
    /// included.  Please refer to the EXTENSION_PREFIX static field
    /// contents for an up-to-date list.  As of the initial release
    /// of the extension system, the prefix defines the following
    /// extensions:
    /// <ul>
    /// <li>CoreExtension
    /// <li>SocketExtension
    /// <li>StdinExtension
    /// </ul>
    /// <p>
    /// <strong>Note:</strong> Compilation requires the installation of
    /// <a href="http://jakarta.apache.org/bcel/" target=_TOP>The Apache Byte Code Engineering Library (BCEL)</a>.
    /// Please see the AwkCompilerImpl Javadocs or the
    /// project webpage for more details.
    /// </remarks>
    /// <seealso cref="org.jawk.backend.AVM">org.jawk.backend.AVM</seealso>
    /// <seealso cref="org.jawk.backend.AwkCompilerImpl">org.jawk.backend.AwkCompilerImpl
    /// 	</seealso>
    /// <author>Danny Daglas</author>
    public class Awk
    {
        private static readonly bool VERBOSE = false;

        /// <summary>
        /// An entry point to Jawk that provides the exit code of the script
        /// if interpreted or an compiler error status if compiled.
        /// </summary>
        /// <remarks>
        /// An entry point to Jawk that provides the exit code of the script
        /// if interpreted or an compiler error status if compiled.
        /// If compiled, a non-zero exit status indicates that there
        /// was a compilation problem.
        /// </remarks>
        /// <param name="args">Command line arguments to the VM.</param>
        /// <returns>
        /// The exit code to the script if interpreted, or exit code
        /// of the compiler.
        /// </returns>
        /// <exception cref="System.IO.IOException">upon an IO error.</exception>
        /// <exception cref="System.TypeLoadException">
        /// if compilation is requested,
        /// but no compilation implementation class is found.
        /// </exception>
        public static int Invoke(string[] args)
        {
            lock (gLocker)
            {
                org.jawk.util.AwkParameters parameters = new org.jawk.util.AwkParameters(typeof(org.jawk.Awk), args, null);

                return Invoke(parameters);
            }
        }



        private static object gLocker = new object();

        public static int Invoke(string script, string data, out string stdout, out string stderr)
        {
            lock (gLocker)
            {
                org.jawk.util.AwkParameters parameters = new org.jawk.util.AwkParameters()
                {
                    script_reader = new StringReader(script),
                    user_extensions = true
                };

                using (MemoryStream inputStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(data)))
                {
                    parameters.name_value_filename_list.Add(inputStream);

                    StringBuilder output = new StringBuilder();

                    using (TextWriter os = new StringWriter(output))
                    {
                        Console.SetOut(os);

                        StringBuilder error = new StringBuilder();

                        using (TextWriter es = new StringWriter(error))
                        {
                            Console.SetError(es);


                            int returnCode = Invoke(parameters);

                            stdout = output.ToString();
                            stderr = error.ToString();

                            return returnCode;

                        }
                    }
                }
            }
        }

        private static int Invoke(org.jawk.util.AwkParameters parameters)
        {
            org.jawk.backend.AVM avm = null;
            try
            {

                // null = NO extension description ==> require awk script
                // key = Keyword, value = JawkExtension
                System.Collections.Generic.IDictionary<string, org.jawk.ext.IJawkExtension> extensions;
                if (parameters.user_extensions)
                {
                    extensions = GetJawkExtensions();
                    if (VERBOSE)
                    {
                        System.Console.Error.WriteLine("(user extensions = " + extensions.Keys + ")");
                    }
                }
                else
                {
                    extensions = new Dictionary<string, IJawkExtension>();
                }
                //if (VERBOSE) System.err.println("(user extensions not enabled)");
                org.jawk.intermediate.AwkTuples tuples = new org.jawk.intermediate.AwkTuples();
                // to be defined below
                if (parameters.IsIntermediateFile())
                {
                    // read the intermediate file, bypassing frontend processing
                    tuples = (org.jawk.intermediate.AwkTuples)ReadObjectFromInputStream(parameters.IfInputStream());
                }
                else
                {
                    org.jawk.frontend.AwkParser parser = new org.jawk.frontend.AwkParser(parameters.additional_functions
                        , parameters.additional_type_functions, parameters.no_input, extensions);
                    // parse the script
                    org.jawk.frontend.IAwkSyntaxTree ast = parser.Parse(parameters.ScriptReader());
                    if (parameters.dump_syntax_tree)
                    {
                        // dump the syntax tree of the script to a file
                        string filename = parameters.OutputFilename("syntax_tree.lst");
                        System.Console.Error.WriteLine("(writing to '" + filename + "')");
                        System.IO.TextWriter ps = new System.IO.StreamWriter(filename);
                        if (ast != null)
                        {
                            ast.Dump(ps);
                        }
                        ps.Close();
                        return 0;
                    }
                    // otherwise, attempt to traverse the syntax tree and build
                    // the intermediate code
                    if (ast != null)
                    {
                        // 1st pass to tie actual parameters to back-referenced formal parameters
                        ast.SemanticAnalysis();
                        // 2nd pass to tie actual parameters to forward-referenced formal parameters
                        ast.SemanticAnalysis();
                        // build tuples
                        int result = ast.PopulateTuples(tuples);
                        // ASSERTION: NOTHING should be left on the operand stack ...
                        //assert result == 0;
                        // Assign queue.next to the next element in the queue.
                        // Calls touch(...) per Tuple so that addresses can be normalized/assigned/allocated
                        tuples.PostProcess();
                        // record global_var -> offset mapping into the tuples
                        // so that the interpreter/compiler can assign variables
                        // on the "file list input" command line
                        parser.PopulateGlobalVariableNameToOffsetMappings(tuples);
                    }
                    if (parameters.write_to_intermediate_file)
                    {
                        // dump the intermediate code to an intermediate code file
                        string filename = parameters.OutputFilename("a.ai");
                        System.Console.Error.WriteLine("(writing to '" + filename + "')");
                        WriteObjectToFile(tuples, filename);
                        return 0;
                    }
                }
                if (parameters.dump_intermediate_code)
                {
                    // dump the intermediate code to a human-readable text file
                    string filename = parameters.OutputFilename("avm.lst");
                    System.Console.Error.WriteLine("(writing to '" + filename + "')");

                    System.IO.TextWriter ps = new System.IO.StreamWriter(filename);
                    tuples.Dump(ps);
                    ps.Close();
                    return 0;
                }
                if (parameters.should_compile || parameters.should_compile_and_run)
                {
                    // compile!
                    int retcode = AttemptToCompile(parameters, tuples);
                    if (retcode != 0)
                    {
                        return retcode;
                    }
                    if (parameters.should_compile_and_run)
                    {
                        return AttemptToExecuteCompiledResult(parameters);
                    }
                    else
                    {
                        return retcode;
                    }
                }
                else
                {
                    // interpret!
                    avm = new org.jawk.backend.AVM(parameters, extensions);
                    return avm.Interpret(tuples);
                }
            }
            catch (System.Exception err)
            {
                System.Console.Out.WriteLine(err.ToString());
                return 1;
            }
            finally
            {
                if (avm != null)
                {
                    avm.WaitForIO();
                }
            }
        }

        
        /// <summary>Use reflection in attempt to access the compiler.</summary>
        /// <remarks>Use reflection in attempt to access the compiler.</remarks>
        private static int AttemptToCompile(org.jawk.util.AwkParameters parameters, org.jawk.intermediate.AwkTuples
             tuples)
        {

            try
            {
                if (VERBOSE)
                {
                    System.Console.Error.WriteLine("(locating AwkCompilerImpl...)");
                }
                System.Type compiler_class = typeof(org.jawk.backend.AwkCompilerImpl);

                if (VERBOSE)
                {
                    System.Console.Error.WriteLine("(found: " + compiler_class + ")");
                }
                try
                {
                    System.Reflection.ConstructorInfo constructor = compiler_class.GetConstructor(new Type[] { typeof(org.jawk.util.AwkParameters) });
                    try
                    {
                        if (VERBOSE)
                        {
                            System.Console.Error.WriteLine("(allocating new instance of the AwkCompiler class...)"
                                );
                        }

                        org.jawk.backend.IAwkCompiler compiler = (org.jawk.backend.IAwkCompiler)constructor.Invoke(new object[] { parameters });
                        if (VERBOSE)
                        {
                            System.Console.Error.WriteLine("(allocated: " + compiler + ")");
                        }
                        if (VERBOSE)
                        {
                            System.Console.Error.WriteLine("(compiling...)");
                        }
                        compiler.Compile(tuples);
                        if (VERBOSE)
                        {
                            System.Console.Error.WriteLine("(done)");
                        }
                        return 0;
                    }
                    //catch (java.lang.InstantiationException ie)
                    //{
                    //    throw new System.Exception("Cannot instantiate the compiler: " + ie);
                    //}
                    catch (System.MemberAccessException iae)
                    {
                        throw new System.Exception("Cannot instantiate the compiler: " + iae);
                    }
                    catch (System.Reflection.TargetInvocationException ite)
                    {
                        throw new System.Exception("Cannot instantiate the compiler: " + ite);
                    }
                }
                catch (System.MissingMethodException nsme)
                {
                    throw new System.Exception("Cannot find the constructor: " + nsme);
                }
            }
            catch (System.TypeLoadException)
            {
                throw new System.ArgumentException("Cannot find the AwkCompiler.");
            }
        }

        private static int AttemptToExecuteCompiledResult(org.jawk.util.AwkParameters parameters
            )
        {
            string classname = parameters.OutputFilename("AwkScript");
            try
            {
                if (VERBOSE)
                {
                    System.Console.Error.WriteLine("(locating " + classname + "...)");
                }
                System.Type script_class;
                string dest_directory = parameters.DestDirectory();
                if (dest_directory == null)
                {
                    script_class = Type.GetType(classname);
                    if (VERBOSE)
                    {
                        System.Console.Error.WriteLine("(found: " + script_class + ")");
                    }
                }
                else
                {
                    org.jawk.util.DestDirClassLoader cl = new org.jawk.util.DestDirClassLoader(dest_directory);
                    script_class = cl.LoadClass(classname);
                    if (VERBOSE)
                    {
                        System.Console.Error.WriteLine("(found: " + script_class + " in " + dest_directory + ")");
                    }
                }
                try
                {
                    System.Reflection.ConstructorInfo constructor = script_class.GetConstructor(new Type[0]);
                    try
                    {
                        if (VERBOSE)
                        {
                            System.Console.Error.WriteLine("(allocating and executing new instance of " + classname
                                 + " class...)");
                        }
                        object obj = constructor.Invoke(new object[0]);

                        System.Reflection.MethodInfo method = script_class.GetMethod("ScriptMain", new Type[] { typeof(org.jawk.util.AwkParameters) });

                        object result = method.Invoke(obj, new object[] { parameters });
                        return 0;
                    }
                    //catch (java.lang.InstantiationException ie)
                    //{
                    //    throw new System.Exception("Cannot instantiate the script: " + ie);
                    //}
                    catch (System.MemberAccessException iae)
                    {
                        throw new System.Exception("Cannot instantiate the script: " + iae);
                    }
                    catch (System.Reflection.TargetInvocationException ite)
                    {
                        System.Exception exception = ite.InnerException;
                        if (exception == null)
                        {
                            throw new System.Exception("Cannot instantiate the script: " + ite);
                        }
                        else
                        {
                            Console.WriteLine(exception.ToString());
                            throw new System.Exception("Cannot instantiate the script: " + exception);
                        }
                    }
                }
                catch (System.MissingMethodException nsme)
                {
                    throw new System.Exception("Cannot find the constructor: " + nsme);
                }
            }
            catch (System.TypeLoadException)
            {
                throw new System.ArgumentException("Cannot find the " + classname + " class.");
            }
        }

        /// <exception cref="System.IO.IOException"></exception>
        /// <exception cref="System.TypeLoadException"></exception>
        private static object ReadObjectFromInputStream(Stream @is)
        {
            BinaryFormatter bformatter = new BinaryFormatter();
            return bformatter.Deserialize(@is);
        }

        /// <exception cref="System.IO.IOException"></exception>
        private static void WriteObjectToFile(object @object, string filename)
        {
            using (Stream stream = File.Open("EmployeeInfo.osl", FileMode.Create))
            {
                BinaryFormatter bformatter = new BinaryFormatter();
                bformatter.Serialize(stream, @object);
            }
        }

        static Awk()
        {
            string jAwkVerbose = ConfigurationManager.AppSettings["jawk.verbose"];
            bool.TryParse(jAwkVerbose, out VERBOSE);
        }

        /// <summary>
        /// Prohibit the instantiation of this class, other than the
        /// way required by JSR 223.
        /// </summary>
        /// <remarks>
        /// Prohibit the instantiation of this class, other than the
        /// way required by JSR 223.
        /// </remarks>
        private Awk()
        {
        }

        private static System.Collections.Generic.IDictionary<string, org.jawk.ext.IJawkExtension> GetJawkExtensions()
        {
            string extensions_string = ConfigurationManager.AppSettings["jawk.extensions"];

            IDictionary<string, IJawkExtension> retval = new Dictionary<string, IJawkExtension>();
            if (string.IsNullOrEmpty(extensions_string))
            {
                return retval;
            }

            // use reflection to obtain extensions

            System.Collections.Generic.ICollection<System.Type> extension_classes = new List<Type>();
            java.util.StringTokenizer st = new java.util.StringTokenizer(extensions_string, "#");
            while (st.HasMoreTokens())
            {
                string cls = st.NextToken();
                if (VERBOSE)
                {
                    System.Console.Out.WriteLine("{cls = " + cls + "}");
                }
                try
                {

                    System.Type c = Type.GetType(cls);
                    // check if it's a JawkException
                    if (!typeof(org.jawk.ext.IJawkExtension).IsAssignableFrom(c))
                    {
                        throw new System.TypeLoadException(cls + " does not implement JawkExtension");
                    }
                    if (extension_classes.Contains(c))
                    {
                        System.Console.Error.WriteLine("Warning: " + cls + " multiply referred in extension class list.  Skipping."
                            );
                        continue;
                    }
                    else
                    {
                        extension_classes.Add(c);
                    }
                    // it is...
                    // create a new instance and put it here
                    try
                    {
                        org.jawk.ext.IJawkExtension ji = (org.jawk.ext.IJawkExtension)System.Activator.CreateInstance
                            (c);
                        string[] keywords = ji.ExtensionKeywords();
                        foreach (string keyword in keywords)
                        {
                            if (retval.ContainsKey(keyword))
                            {
                                throw new System.ArgumentException("keyword collision : " + keyword + " for both "
                                     + retval[keyword].GetExtensionName() + " and " + ji.GetExtensionName());
                            }
                            retval[keyword] = ji;
                        }
                    }
                    //catch (java.lang.InstantiationException ie)
                    //{
                    //    Awk.Error.WriteLine("Cannot instantiate " + c + " : " + ie);
                    //}
                    catch (System.MemberAccessException iae)
                    {
                        System.Console.Error.WriteLine("Cannot instantiate " + c + " : " + iae);
                    }
                }
                catch (System.TypeLoadException cnfe)
                {
                    System.Console.Error.WriteLine("Cannot classload " + cls + " : " + cnfe);
                }
            }
            return retval;
        }
    }
}
